﻿using Abp.Application.Services.Dto;
using Abp.Authorization;
using Abp.Domain.Repositories;
using Abp.Domain.Uow;
using Abp.Extensions;
using Abp.Localization;
using Abp.MultiTenancy;
using Abp.UI;
using Mt.Site.Application.Localization.Dto;
using Mt.Site.Core.Authorization;
using Mt.Site.Core.Base.Localization;
using Mt.Site.Core.Localization;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Linq.Dynamic.Core;
using System.Threading.Tasks;

namespace Mt.Site.Application.Localization
{
    [AbpAuthorize(AppPermissions.Pages_Administration_Languages)]
    public class LanguageAppService : MtSiteAppServiceBase, ILanguageAppService
    {
        private readonly IApplicationLanguageManager _applicationLanguageManager;
        private readonly IApplicationLanguageTextManager _applicationLanguageTextManager;
        private readonly IRepository<MtApplicationLanguage> _languageRepository;
        private readonly IApplicationCulturesProvider _applicationCulturesProvider;

        public LanguageAppService(
            IApplicationLanguageManager applicationLanguageManager,
            IApplicationLanguageTextManager applicationLanguageTextManager,
            IRepository<MtApplicationLanguage> languageRepository,
            IApplicationCulturesProvider applicationCulturesProvider)
        {
            _applicationLanguageManager = applicationLanguageManager;
            _languageRepository = languageRepository;
            _applicationLanguageTextManager = applicationLanguageTextManager;
            _applicationCulturesProvider = applicationCulturesProvider;
        }

        public async Task<GetLanguagesOutput> GetLanguages()
        {
            //改为仅读取当前租户自身的语种，（不包含host中的语种）
            var languages = (await _applicationLanguageManager.GetLanguagesOfSingleTenantAsync(AbpSession.TenantId))
                .OrderBy(l => l.DisplayName);

            var defaultLanguage = await _applicationLanguageManager.GetDefaultLanguageOrNullAsync(AbpSession.TenantId);

            return new GetLanguagesOutput(
                ObjectMapper.Map<List<ApplicationLanguageListDto>>(languages),
                defaultLanguage?.Name
                );
        }

        public async Task<GetLanguagesOutput> GetLanguages2()
        {
            //拉取当前分站涉及的所有雨中（包含host中的语种）， 为了修改默认语种
            var languages = (await _applicationLanguageManager.GetLanguagesAsync(AbpSession.TenantId))
                .OrderBy(l => l.DisplayName);

            var defaultLanguage = await _applicationLanguageManager.GetDefaultLanguageOrNullAsync(AbpSession.TenantId);

            return new GetLanguagesOutput(
                ObjectMapper.Map<List<ApplicationLanguageListDto>>(languages),
                defaultLanguage?.Name
                );
        }


        [AbpAuthorize(AppPermissions.Pages_Administration_Languages_Create, 
            AppPermissions.Pages_Administration_Languages_Edit)]
        public async Task<GetLanguageForEditOutput> GetLanguageForEdit(NullableIdDto input)
        {
            MtApplicationLanguage language = null;
            if (input.Id.HasValue)
            {
                language = await _languageRepository.GetAsync(input.Id.Value);
            }

            var output = new GetLanguageForEditOutput();
            //当前后台界面的语种，仅用于对比测试
            output.CurrentCultureName = CultureInfo.CurrentUICulture.Name;

            //Language
            output.Language = language != null
                ? ObjectMapper.Map<ApplicationLanguageEditDto>(language)
                : new ApplicationLanguageEditDto();

            //Language names
            output.LanguageNames = _applicationCulturesProvider
                .GetAllCultures()
                .Select(c => new ComboboxItemDto(c.Name, c.EnglishName + " (" + c.Name + ")") 
                    { IsSelected = output.Language.Name == c.Name })
                .ToList();

            //Flags
            output.Flags = FamFamFamFlagsHelper
                .FlagClassNames
                .OrderBy(f => f)
                .Select(f => new ComboboxItemDto(f, FamFamFamFlagsHelper.GetCountryCode(f)) 
                    { IsSelected = output.Language.Icon == f})
                .ToList();

            output.Sites = await GetSitesForSelectAFatherSite(input.Id, output.Language.SourceLanguageId);

            return output;
        }

        private async Task<List<ComboboxItemDto>> GetSitesForSelectAFatherSite(int? id, int? sourceLanguageId)
        {
            using (CurrentUnitOfWork.DisableFilter(
                AbpDataFilters.MayHaveTenant,
                AbpDataFilters.MustHaveTenant))
            {
                //拉取所有语种清单
                var all = await _languageRepository.GetAllListAsync(u=>u.IsDisabled == false && u.TenantId != null);
                if (id.HasValue) //需过滤掉以当前分站作为上级的分站，避免构成闭环。
                {
                    var idList = all.Select(u => u.Id).ToList(); //参照

                    foreach (var item in all)
                    {
                        //如果是当前分站（+语种） 或是已验证为不合法的选项
                        if(item.Id  == id || !idList.Contains(item.Id))
                        {
                            continue;
                        }

                        var tempItem = item;
                        var tempList = new List<int>();

                        while (tempItem != null && tempItem.SourceLanguageId != null)
                        {
                            tempList.Add(tempItem.Id);
                            tempItem = all.FirstOrDefault(c => c.Id == tempItem.SourceLanguageId);
                            if (tempItem != null && (tempItem.Id == id || !idList.Contains(tempItem.Id)))
                            {
                                foreach (var itemId in tempList)
                                {
                                    idList.Remove(itemId);
                                }

                                tempItem = null;
                            }
                        }
                    }
                    idList.Remove(id.Value);

                    return all.Where(u=>idList.Contains(u.Id))
                        .OrderBy(c => c.DisplayName)
                        .Select(f => new ComboboxItemDto(f.Id.ToString(), f.DisplayName) { IsSelected = sourceLanguageId == f.Id })
                        .ToList();
                }
                else
                {
                    return all.OrderBy(c => c.DisplayName)
                    .Select(f => new ComboboxItemDto(f.Id.ToString(), f.DisplayName))
                    .ToList();
                }
            }
        }

        /**
         *  本地化字符串的查找：
         *  1. 语种的确定：
         *     假设当前用户访问的界面语种为en-US, 先用en-US查找，
         *     若未找到则查找相同语言的其他本地化配置（无国家编码，即en）,
         *     还找不到则用默认语言查找 （每个分站甚至每个用户都可以设置自己的默认语言，目前未提供用户的定制, 应用层面的默认语种为en）
         *  2. 在某语种的配置字典中查找特定key对应的值：
         *     先查找当前租户在数据库中的文本，
         *     再回退到host的数据库中的文本，
         *     再回退到xml中的默认值，
         *     返回null
         *    ---------------
         *   框架中的实现：
         *    1. 获取当前租户的所有语种及默认语种
         *       所有语种： 先获取host的所有语种（数据库）组成一个dictionary（Name作为key），
         *                  再获取当前租户的所有语种（数据库）覆盖入这个字典
         *            PS: 框架中默认租户会共享host的语种，所以在为租户添加新语种时，如果这个语种在host中已存在，则会报错
         *            
         *    改动：
         *      1. 将host的语种固定为后台界面使用，而租户仅使用它自身配置的语种来展示内容，不继承host的语种
         *      2. 所以原框架中的语种增删改方法都不能再使用
         *      
         *   -----------------
         *   en-US的GetAllStrings，先加载默认语种中的所有，再加载en的所有覆盖进去，再加载en-US的所有覆盖进去   
         *   
         */
        public async Task CreateOrUpdateLanguage(CreateOrUpdateLanguageInput input)
        {
            if (input.Language.Id.HasValue)
            {
                await UpdateLanguageAsync(input);
            }
            else
            {
                await CreateLanguageAsync(input);
            }
        }

        [AbpAuthorize(AppPermissions.Pages_Administration_Languages_Delete)]
        public async Task DeleteLanguage(EntityDto input)
        {
            //租户的语种仅当前租户及host的管理员可删, 而host用户是可以模拟租户登陆的
            await _languageRepository.DeleteAsync(input.Id);
        }

        [AbpAuthorize(AppPermissions.Pages_Administration_Languages_ChangeDefaultLanguage)]
        public async Task SetDefaultLanguage(SetDefaultLanguageInput input)
        {
            await _applicationLanguageManager.SetDefaultLanguageAsync(
                AbpSession.TenantId,
                CultureHelper.GetCultureInfoByChecking(input.Name).Name
                );
        }
        [AbpAuthorize(AppPermissions.Pages_Administration_Languages_Edit)]
        public async Task ResetDefaultLanguage()
        {
            var defaultLanguage = await SettingManager.GetSettingValueForApplicationAsync(LocalizationSettingNames.DefaultLanguage);
            await _applicationLanguageManager.SetDefaultLanguageAsync(
                AbpSession.TenantId,
                defaultLanguage
                );
        }

        [AbpAuthorize(AppPermissions.Pages_Administration_Languages_ChangeTexts)]
        public async Task<PagedResultDto<LanguageTextListDto>> GetLanguageTexts(GetLanguageTextsInput input)
        {
            /* Note: This method is used by SPA without paging, MPA with paging.
             * So, it can both usable with paging or not */

            //Normalize base language name
            if (input.BaseLanguageName.IsNullOrEmpty())
            {
                var defaultLanguage = 
                    await _applicationLanguageManager.GetDefaultLanguageOrNullAsync(AbpSession.TenantId);
                if (defaultLanguage == null)
                {
                    defaultLanguage = (await _applicationLanguageManager.GetLanguagesAsync(AbpSession.TenantId))
                        .FirstOrDefault();
                    if (defaultLanguage == null)
                    {
                        throw new Exception("No language found in the application!");
                    }
                }

                input.BaseLanguageName = defaultLanguage.Name;
            }

            var source = LocalizationManager.GetSource(input.SourceName);
            var baseCulture = CultureInfo.GetCultureInfo(input.BaseLanguageName);
            var targetCulture = CultureInfo.GetCultureInfo(input.TargetLanguageName);

            var allStrings = source.GetAllStrings();
            var baseValues = _applicationLanguageTextManager.GetStringsOrNull(
                AbpSession.TenantId,
                source.Name,
                baseCulture,
                allStrings.Select(x => x.Name).ToList()
            );

            var targetValues = _applicationLanguageTextManager.GetStringsOrNull(
                AbpSession.TenantId,
                source.Name,
                targetCulture,
                allStrings.Select(x => x.Name).ToList()
            );

            var languageTexts = allStrings.Select((t, i) => new LanguageTextListDto
            {
                Key = t.Name,
                BaseValue = GetValueOrNull(baseValues, i),
                TargetValue = GetValueOrNull(targetValues, i) ?? GetValueOrNull(baseValues, i)
            }).AsQueryable();

            //Filters
            if (input.TargetValueFilter == "EMPTY")
            {
                languageTexts = languageTexts.Where(s => s.TargetValue.IsNullOrEmpty());
            }

            if (!input.FilterText.IsNullOrEmpty())
            {
                languageTexts = languageTexts.Where(
                    l => (l.Key != null && l.Key.IndexOf(input.FilterText, StringComparison.CurrentCultureIgnoreCase) >= 0) ||
                         (l.BaseValue != null && l.BaseValue.IndexOf(input.FilterText, StringComparison.CurrentCultureIgnoreCase) >= 0) ||
                         (l.TargetValue != null && l.TargetValue.IndexOf(input.FilterText, StringComparison.CurrentCultureIgnoreCase) >= 0)
                    );
            }

            var totalCount = languageTexts.Count();

            //Ordering
            if (!input.Sorting.IsNullOrEmpty())
            {
                languageTexts = languageTexts.OrderBy(input.Sorting);
            }

            //Paging
            if (input.SkipCount > 0)
            {
                languageTexts = languageTexts.Skip(input.SkipCount);
            }

            if (input.MaxResultCount > 0)
            {
                languageTexts = languageTexts.Take(input.MaxResultCount);
            }

            return new PagedResultDto<LanguageTextListDto>(
                totalCount,
                languageTexts.ToList()
                );
        }

        [AbpAuthorize(AppPermissions.Pages_Administration_Languages_ChangeTexts)]
        public async Task UpdateLanguageText(UpdateLanguageTextInput input)
        {
            var culture = CultureHelper.GetCultureInfoByChecking(input.LanguageName);
            var source = LocalizationManager.GetSource(input.SourceName);
            await _applicationLanguageTextManager.UpdateStringAsync(
                AbpSession.TenantId, 
                source.Name, 
                culture, 
                input.Key, 
                input.Value
                );
        }

        [AbpAuthorize(AppPermissions.Pages_Administration_Languages_ChangeTexts)]
        public async Task ResetLanguageText(UpdateLanguageTextInput input)
        {
            var culture = CultureHelper.GetCultureInfoByChecking(input.LanguageName);
            var source = LocalizationManager.GetSource(input.SourceName);
            await _applicationLanguageTextManager.BackToDefaultAsync(AbpSession.TenantId, source.Name, culture, input.Key);
        }

        [AbpAuthorize(AppPermissions.Pages_Administration_Languages_Create)]
        protected virtual async Task CreateLanguageAsync(CreateOrUpdateLanguageInput input)
        {
            if (AbpSession.MultiTenancySide == MultiTenancySides.Host)
            {
                throw new UserFriendlyException(L("HostCannotCreateLanguage"));
            }

            var culture = CultureHelper.GetCultureInfoByChecking(input.Language.Name);
            await CheckLanguageIfAlreadyExists(culture.Name);

            var displayName = !string.IsNullOrWhiteSpace(input.Language.DisplayName) ? input.Language.DisplayName : culture.DisplayName;
            var newLanguage = new MtApplicationLanguage(
                    AbpSession.TenantId,
                    culture.Name,
                    displayName,
                    input.Language.Icon
                    )
            {
                IsDisabled = !input.Language.IsEnabled,
                SourceLanguageId = input.Language.SourceLanguageId
            };

            await _languageRepository.InsertAsync(newLanguage);
        }

        [AbpAuthorize(AppPermissions.Pages_Administration_Languages_Edit)]
        protected virtual async Task UpdateLanguageAsync(CreateOrUpdateLanguageInput input)
        {
            Debug.Assert(input.Language.Id != null, "input.Language.Id != null");

            var culture = CultureHelper.GetCultureInfoByChecking(input.Language.Name);

            await CheckLanguageIfAlreadyExists(culture.Name, input.Language.Id.Value);

            var language = await _languageRepository.GetAsync(input.Language.Id.Value);

            var displayName = !string.IsNullOrWhiteSpace(input.Language.DisplayName) ? input.Language.DisplayName : culture.DisplayName;

            language.Name = culture.Name;
            language.DisplayName = displayName;
            language.Icon = input.Language.Icon;
            language.SourceLanguageId = input.Language.SourceLanguageId;
            language.IsDisabled = !input.Language.IsEnabled;

            await _languageRepository.UpdateAsync(language);
        }

        private async Task CheckLanguageIfAlreadyExists(string languageName, int? expectedId = null)
        {
            IReadOnlyList<ApplicationLanguage> languages;
            if (AbpSession.MultiTenancySide != MultiTenancySides.Host)
            {
                languages = await _applicationLanguageManager.GetLanguagesOfSingleTenantAsync(AbpSession.TenantId);
            }
            else
            {
                languages = await _applicationLanguageManager.GetLanguagesAsync(null);
            }
            var existingLanguage = languages.FirstOrDefault(l => l.Name == languageName);

            if (existingLanguage == null)
            {
                return;
            }

            if (expectedId != null && existingLanguage.Id == expectedId.Value)
            {
                return;
            }

            throw new UserFriendlyException(L("ThisLanguageAlreadyExists"));
        }

        private string GetValueOrNull(List<string> items, int index)
        {
            return items.Count > index ? items[index] : null;
        }
    }
}